import fs from 'fs-extra'
import { Context } from 'koa'
import _, { isArray, isString } from 'lodash'
import path from 'path'
import { FileType, GetNasFileListParams, NasFile, UploadNasFileForm } from '../../../../global/types'
import { systemLogger } from '../../logs/logger'
import saveFile from '../../util/fs/saveFile'
import Reg from '../../../../global/Reg'

const rootPath = path.join(__dirname, '../../public/static/users')

export default {
	/** 根据 id 获取文件 */
	getFile,
	/** 获取文件/目录列表 */
	getList,
	/** 获取文件夹下所有文件和文件夹 */
	getListDeep,
	/** 上传文件 */
	uploadFile,
	/** 创建文件夹 */
	createDir,
	/** 删除文件 */
	delFiles,
	/** 重命名文件 */
	renameFile,
	/** 复制文件 */
	copyFiles
}

/**
 * 深度创建文件
 * @param uid 用户ID
 * @param parentPath 文件目录
 * @returns 文件夹ID
 */
const createDirDeep = async (ctx: Context) => {
	const uid = ctx.userInfo!.id
	const { dirPath, parentPath } = ctx.request.body
	const createDirName: string = dirPath || parentPath
	// 获取文件目录的所有父级目录列表
	const pathList = createDirName.match(/[^@]+@@@/g)?.reduce((list, cut, i) => {
		list.push((list[i - 1] || '') + cut)
		return list
	}, [] as string[])
	if (!pathList) return
	// 循环父级目录
	for (const i in pathList) {
		const filePath = pathList[i]
		const fileName = filePath.split('@@@').at(-2)
		const queryDirPath = await ctx.db.selectOne(
			`SELECT id FROM nas_files WHERE uid=${uid} AND filePath="${filePath}" AND fileType=${FileType.dir}`
		)
		// 如果目录不存在则创建目录
		if (!queryDirPath) {
			// 获取父级ID
			const parentPath = pathList[+i - 1] || ''
			// 插入新目录
			const insertId = await ctx.db.insert(
				`INSERT INTO nas_files (uid, fileName, filePath, parentPath, fileType, uploadFinish) VALUES (${uid}, "${fileName}", "${filePath}", "${parentPath}", ${FileType.dir}, true)`
			)
			// 如果是最后一个目录，返回当前目录ID
			if (+i + 1 === pathList.length) return insertId
		}
	}
	const { id } = await ctx.db.selectOne(`SELECT id FROM nas_files WHERE uid=${uid} AND filePath="${pathList.at(-1)}"`)
	return id
}

async function getFile(ctx: Context) {
	const { id, filePath } = ctx.request.query
	if (!id && !filePath) return ctx.error('缺少 ID 或 文件路径')
	let fileInfo = null
	if (id) fileInfo = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE id=${id}`)
	else if (filePath) {
		const uid = ctx.userInfo!.id
		fileInfo = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE uid=${uid} AND filePath="${filePath}"`)
	}
	ctx.return(fileInfo)
}

async function getListHandler(ctx: Context) {
	let { page, size, parentPath, fileType } = ctx.request.query as unknown as GetNasFileListParams
	if (!page) page = 1
	page = +page
	if (!size) size = 10
	if (!parentPath) parentPath = ''
	const uid = ctx.userInfo!.id
	let list: (NasFile & { hasChild?: boolean })[]
	let total = 0
	// 文件分类查询
	if (fileType) {
		list = await ctx.db.select(
			`SELECT * FROM nas_files WHERE uid=${uid} AND fileType=${fileType} ORDER BY uploadFinish ASC, id ASC LIMIT ${
				(page - 1) * size
			}, ${size}`
		)
		total = await ctx.db.count(
			`SELECT COUNT(*) FROM nas_files WHERE uid=${uid} AND fileType=${fileType} ORDER BY uploadFinish ASC`
		)
	} else {
		// 父文件夹查询
		list = await ctx.db.select(
			`SELECT * FROM nas_files WHERE uid=${uid} AND parentPath="${parentPath}" ORDER BY uploadFinish ASC, id ASC LIMIT ${
				(page - 1) * size
			}, ${size}`
		)
		total = await ctx.db.count(`SELECT COUNT(*) FROM nas_files WHERE uid=${uid} AND parentPath="${parentPath}"`)
	}

	for (const i in list) {
		const item = list[i]
		if (item.fileType === FileType.dir) {
			const hasChild = await ctx.db.selectOne(
				`SELECT * FROM nas_files WHERE uid=${uid} AND parentPath LIKE "${item.filePath}%"`
			)
			if (hasChild) item.hasChild = true
		}
	}

	return { page, list, total }
}

async function getList(ctx: Context) {
	const res = await getListHandler(ctx)
	ctx.return(res)
}

async function getListDeep(ctx: Context) {
	const parentPath = ctx.request.query.parentPath || ''
	const uid = ctx.userInfo!.id
	const files = await ctx.db.select(`SELECT * FROM nas_files WHERE uid=${uid} AND parentPath LIKE "${parentPath}%"`)
	ctx.return(files)
}

async function uploadFile(ctx: Context) {
	const file = ctx.request.files?.file as unknown as File
	const { id } = ctx.request.body as UploadNasFileForm
	if (id && !file) return ctx.error('缺少需要上传的文件')
	const uid = ctx.userInfo!.id + ''
	const isFirstUpload = !!id

	// 开始上传
	if (!isFirstUpload) {
		let { fileSize, fileType, fileName, parentPath } = ctx.request.body as UploadNasFileForm
		if (!fileName) return ctx.error('缺少文件名')
		if (!fileSize) return ctx.error('缺少文件大小')
		if (!fileType) return ctx.error('缺少文件类型')
		if (/@@@/.test(fileName)) return ctx.error('文件名不能包含@@@')
		if (!Reg.isNum(fileSize)) return ctx.error('无效的文件大小')
		if (!parentPath) parentPath = ''
		if (!_.inRange(+fileType, 1, 8)) return ctx.error('无效的文件类型')
		else if (!Reg.isNasParentPath(parentPath)) return ctx.error('无效的文件目录')
		const filePath = parentPath + fileName

		const isExist = await ctx.db.selectOne(`SELECT id FROM nas_files WHERE uid=${uid} AND filePath="${filePath}"`)
		if (isExist) return ctx.error(`文件${fileName}已存在`)

		// 深度创建文件夹
		if (parentPath) await createDirDeep(ctx)

		// 插入数据库
		if (!fileType) fileType = FileType.other
		const insertQuery = `INSERT INTO nas_files (
			uid, fileName, parentPath, filePath, fileSize, fileType, uploaded
		) VALUES (
			${uid}, "${fileName}", "${parentPath}", "${filePath}", "${fileSize}", ${fileType}, 0
		)`
		const insertId = await ctx.db.insert(insertQuery)
		// 上传缩略图
		const thumbnail = ctx.request.files?.thumbnail as unknown as File
		let warning = ''
		if (thumbnail) {
			if (![FileType.image, FileType.video].includes(+fileType)) {
				warning = '只有图片或视频才能上传缩略图'
			} else if (thumbnail.size > 1024 * 1024 * 2) warning = '缩略图不得大于2M'
			else {
				let imgExt = path.extname(thumbnail.name)
				if (+fileType === FileType.video) imgExt = '.jpeg'
				const toPath = path.join('users', uid, 'nas', 'thumbnail')
				await saveFile({
					file: thumbnail,
					fileName: insertId + imgExt,
					toPath
				})
					.then(async () => await ctx.db.update(`UPDATE nas_files SET thumbnailExt="${imgExt}" WHERE id=${insertId}`))
					.catch(err => {
						systemLogger().error(err)
						warning = '上传缩略图失败'
					})
			}
		}
		const data = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE uid=${uid} AND filePath="${filePath}"`)
		// 如果上传缩略图异常
		if (warning) return ctx.warning({ code: 204, data, msg: warning })
		ctx.return(data, 204)
	} else {
		// 断点续传
		const queryFile = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE id=${id}`)
		if (!queryFile) return ctx.error('不存在的文件')
		const { fileName, parentPath, filePath, fileSize } = queryFile
		const toPath = path.join('users', uid, 'nas', 'file')
		const { size } = await saveFile({
			file,
			toPath,
			fileName: parentPath + fileName,
			flags: 'a'
		})
		const isUploadFinish = size >= fileSize
		await ctx.db.update(`UPDATE nas_files SET uploaded=${size}, uploadFinish=${isUploadFinish} WHERE id=${id}`)

		const data = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE uid=${uid} AND filePath="${filePath}"`)
		// 如果上传完毕返回成功提示
		if (isUploadFinish) ctx.success({ data, msg: `成功上传文件${fileName}` })
		// 否则返回更新的文件信息
		else ctx.return(data)
	}
}

async function createDir(ctx: Context) {
	let { dirPath } = ctx.request.body
	if (!dirPath) return ctx.error('缺少文件目录')
	if (!Reg.isNasParentPath(dirPath)) return ctx.error('无效的文件夹名')
	const uid = ctx.userInfo!.id
	const isDirExist = await ctx.db.selectOne(`SELECT id FROM nas_files WHERE uid=${uid} AND filePath="${dirPath}"`)
	if (isDirExist) return ctx.error('已存在的文件目录')
	const dirId = await createDirDeep(ctx)
	if (dirId) {
		ctx.success({
			data: await getListHandler(ctx),
			msg: '成功创建文件夹'
		})
	} else ctx.error('创建文件夹失败')
}

async function delFiles(ctx: Context) {
	const { filePathList } = ctx.request.body
	if (!filePathList) return ctx.error('缺少文件路径列表')
	if (!isArray(filePathList)) return ctx.error('无效的文件路径列表')
	const uid = ctx.userInfo!.id

	for (const i in filePathList) {
		const filePath = filePathList[i]
		const queryList: {
			id: number
			filePath: string
			thumbnailExt: string
		}[] = await ctx.db.select(
			`SELECT id, filePath, thumbnailExt FROM nas_files WHERE uid=${uid} AND filePath LIKE "${filePath}%"`
		)
		for (const i in queryList) {
			const item = queryList[i]
			const realFilePath = path.join(rootPath, `${uid}/nas/file/${item.filePath}`)
			await fs.remove(realFilePath)
			if (item.thumbnailExt) {
				const thumbnaiImgPath = path.join(rootPath, `${uid}/nas/thumbnail/${item.id}${item.thumbnailExt}`)
				await fs.remove(thumbnaiImgPath)
			}
			await ctx.db.delete(`DELETE FROM nas_files WHERE uid=${uid} AND id=${item.id}`)
		}
	}
	ctx.success({
		data: await getListHandler(ctx),
		msg: '成功删除文件'
	})
}

async function renameFile(ctx: Context) {
	const { id, fileName: newFileName } = ctx.request.body
	if (!id) return ctx.error('缺少文件ID')
	if (!newFileName) return ctx.error('缺少文件名')
	const uid = ctx.userInfo!.id
	const { parentPath, filePath, fileType }: NasFile = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE id=${id}`)
	if (fileType !== FileType.dir) {
		const oldPath = path.join(rootPath, `${uid}/nas/file/${filePath}`)
		const newPath = path.join(rootPath, `${uid}/nas/file/${parentPath + newFileName}`)
		await fs.rename(oldPath, newPath)
	}
	const newFilePath = parentPath + newFileName
	await ctx.db.update(`UPDATE nas_files SET fileName="${newFileName}", filePath="${newFilePath}" WHERE id=${id}`)
	ctx.success({
		data: await getListHandler(ctx),
		msg: '成功重命名文件'
	})
}

async function copyFiles(ctx: Context) {
	let {
		filePathList,
		parentPath: targetParentPath
	}: {
		filePathList: string[]
		parentPath: string
	} = ctx.request.body

	if (!filePathList) return ctx.error('缺少文件列表')
	if (!isArray(filePathList)) return ctx.error('非法的文件列表')
	if (!filePathList.every(item => isString(item))) return ctx.error('非法的文件列表')
	if (!targetParentPath) targetParentPath = ''
	const uid = ctx.userInfo!.id

	for (const i in filePathList) {
		const filePath = filePathList[i]
		const info: NasFile = await ctx.db.selectOne(`SELECT * FROM nas_files WHERE uid=${uid} AND filePath="${filePath}"`)

		// 获取唯一文件名
		let targetFileName = info.fileName
		let targetFilePath = ''
		let targetFileIsExist = true
		let existNum = 0
		while (targetFileIsExist) {
			const prefix = existNum ? `copy-${existNum}-` : ''
			let newTargetFileName = prefix + targetFileName
			targetFilePath = targetParentPath + newTargetFileName
			if (info.fileType === FileType.dir) targetFilePath += '@@@'
			targetFileIsExist = await ctx.db.selectOne(
				`SELECT id FROM nas_files WHERE uid=${uid} AND filePath="${targetFilePath}"`
			)
			if (targetFileIsExist) existNum++
			else targetFileName = newTargetFileName
		}

		// 复制文件
		if (info.fileType !== FileType.dir) {
			const sourcePath = path.join(rootPath, `${uid}/nas/file/${info.filePath}`)
			const targetPath = path.join(rootPath, `${uid}/nas/file/${targetFilePath}`)
			await fs.copy(sourcePath, targetPath)
		}

		// 插入数据库
		const insertId = await ctx.db.insert(
			`INSERT INTO nas_files (
				uid, fileName, parentPath, filePath, fileSize, fileType, uploaded, uploadFinish
			) VALUES (
				${uid}, "${targetFileName}", "${targetParentPath}", "${targetFilePath}", ${info.fileSize}, ${info.fileType}, ${info.uploaded}, ${info.uploadFinish}
			)`
		)
		// 复制缩略图
		if (info.thumbnailExt) {
			const ext = info.thumbnailExt
			const sourcePath = path.join(rootPath, `${uid}/nas/thumbnail/${info.id + ext}`)
			const targetPath = path.join(rootPath, `${uid}/nas/thumbnail/${insertId + ext}`)
			await fs.copy(sourcePath, targetPath)
			await ctx.db.update(`UPDATE nas_files SET thumbnailExt="${info.thumbnailExt}" WHERE id=${insertId}`)
		}

		// 复制文件夹下所有内容
		if (info.fileType === FileType.dir) {
			const inDirFileList: NasFile[] = await ctx.db.select(
				`SELECT * FROM nas_files WHERE uid=${uid} AND filePath!="${filePath}" AND filePath LIKE "${filePath}%"`
			)
			for (const i in inDirFileList) {
				const inDirInfo = inDirFileList[i]
				const inDirTargetParentPath = inDirInfo.parentPath.replace(info.filePath, targetFilePath)
				const inDirTargetFilePath = inDirInfo.filePath.replace(info.filePath, targetFilePath)
				if (inDirInfo.fileType !== FileType.dir) {
					const sourcePath = path.join(rootPath, `${uid}/nas/file/${inDirInfo.filePath}`)
					const targetPath = path.join(rootPath, `${uid}/nas/file/${inDirTargetFilePath}`)
					await fs.copy(sourcePath, targetPath)
				}
				// 插入数据库
				const insertId = await ctx.db.insert(
					`INSERT INTO nas_files (
						uid, fileName, parentPath, filePath, fileSize, fileType, uploaded, uploadFinish
					) VALUES (
						${uid}, "${inDirInfo.fileName}", "${inDirTargetParentPath}", "${inDirTargetFilePath}",
						${inDirInfo.fileSize}, ${inDirInfo.fileType}, ${inDirInfo.uploaded}, ${inDirInfo.uploadFinish}
					)`
				)
				// 复制缩略图
				if (inDirInfo.thumbnailExt) {
					const ext = inDirInfo.thumbnailExt
					const sourcePath = path.join(rootPath, `${uid}/nas/thumbnail/${inDirInfo.id + ext}`)
					const targetPath = path.join(rootPath, `${uid}/nas/thumbnail/${insertId + ext}`)
					await fs.copy(sourcePath, targetPath)
					await ctx.db.update(`UPDATE nas_files SET thumbnailExt="${inDirInfo.thumbnailExt}" WHERE id=${insertId}`)
				}
			}
		}
	}
	ctx.success({
		data: await getListHandler(ctx),
		msg: '成功复制文件'
	})
}
